Skip to content

Release 1.4.2a1#54

Open
github-actions[bot] wants to merge 38 commits into
masterfrom
release-1.4.2a1
Open

Release 1.4.2a1#54
github-actions[bot] wants to merge 38 commits into
masterfrom
release-1.4.2a1

Conversation

@github-actions

Copy link
Copy Markdown

Human review requested!

JarbasAl and others added 30 commits April 3, 2026 16:13
…-workshop-9

chore(ovos_adapt_parser): allow ovos-workshop<9.0.0
* feat(test): ovoscope end-to-end tests for AdaptPipeline

Adds an end-to-end test suite for AdaptPipeline using ovoscope's
E2EPipelineHarness. Covers:

- vocab + IntentBuilder registration and required-keyword matching
- missing-keyword no-match and no-intent no-match
- best-of-multiple-intent selection
- optional slots (present / absent)
- detach_intent and detach_skill isolation
- session blacklisted_intents / blacklisted_skills

CI job uses the shared OpenVoiceOS/gh-automations reusable workflow with
require_adapt: true and pre_release: true (the latter is temporary until
ovoscope ships a release containing the E2EPipelineHarness).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): avoid Adapt stop-word filtering in test_utterance_field_preserved

Replace "on"/"turn on the lights" with "enable"/"enable the lights" so
Adapt does not silently drop the keyword as a stop word, causing the
send_and_capture to return None.

Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>

* ci: modernize legacy workflows so PR checks actually run

- Delete build_tests.yml and install_tests.yml: both reference
  mycroft-core-style extras (`[audio-backend,mark1,stt,tts,skills,gui,
  bus,all]`, `tflite_runtime`) that have no relevance to this library
  and have been failing for as long as Python 3.8 has been removed
  from GitHub runners.
- unit_tests.yml: bump python matrix from [3.7,3.8,3.9,'3.10'] to
  ['3.10','3.11','3.12'] (3.7/3.8 are no longer available); upgrade
  actions/checkout v2 -> v6 and setup-python v2 -> v6.
- license_tests.yml: same actions bumps; python 3.8 -> 3.11.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(license): upgrade action and drop 'Error' from fail list

pilosus/action-pip-license-checker@v0.5.0 misclassifies modern packages
(build, click, packaging, urllib3, etc.) as `Error` because it does not
understand the current PEP 639/SPDX license metadata format. Upgrading
to v2.6.2 fixes the classifier; dropping `Error` from the fail list
prevents false positives when a single transitive dep can't be parsed.

Copyleft and Other still fail — those are the categories the check is
actually meant to catch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci(license): pin action to v3.1.0 (v2.6.2 doesn't exist)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: migrate to pyproject.toml + apply shared gh-automations workflows

Drop the legacy setup.py / requirements.txt / MANIFEST.in in favour of
a single pyproject.toml. Append the __version__ assignment to
ovos_adapt/version.py so setuptools' attr-based dynamic versioning can
read it.

Replace the legacy mycroft-style PR workflows with the standard
OpenVoiceOS/gh-automations reusable workflows:

  build-tests.yml         coverage.yml         license_check.yml
  lint.yml                pip_audit.yml        release-preview.yml
  repo-health.yml         opm-check.yml        conventional-label.yml
  publish_stable.yml      release_workflow.yml

Removed: unit_tests.yml, license_tests.yml (replaced by build-tests.yml
and license_check.yml from gh-automations). build_tests.yml and
install_tests.yml had already been deleted earlier in this branch as
mycroft-core cruft.

opm-check.yml uses plugin_type=pipeline (and drops the buggy nested-
quote entry_point input).

ovoscope.yml now uses install_extras=test now that pyproject.toml
declares a [test] optional-dependencies group.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat: expand OVOS template syntax in vocab entries

Wire ovos_utils.bracket_expansion.expand_template into the vocab
registration path so a single entry like

    turn (on|off) the [bright] lights

is expanded into all concrete surface forms before being handed to
the adapt engine. This brings adapt in line with the rest of the
OVOS stack which already supports (a|b) alternatives and [opt]
optionals in templates.

* refactor: inline bracket-expansion helper, drop ovos-utils import

Replace the ovos_utils.bracket_expansion import with a local
ovos_adapt._bracket_expansion module so the plugin does not need
ovos-utils as a runtime dependency just for two regex helpers.

* chore: remove accidentally committed AUDIT.md and egg-info

* refactor: simplify expand_template dedup using set

Order does not matter when feeding training data to the adapt engine;
drop the seen-tracker boilerplate in favour of a plain set.

* refactor: use ovos_utils.bracket_expansion instead of a local copy

ovos-utils is already a dependency, so import expand_template from
ovos_utils.bracket_expansion and drop the inline reimplementation.
Restore pyproject.toml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: expand OVOS template syntax in engine.register_entity

Move bracket-template expansion from the OPM pipeline wrapper into
IntentDeterminationEngine.register_entity so it applies to every
caller, including DomainIntentDeterminationEngine and direct engine
use, not just the pipeline path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: vendor expand_template in the engine module

Keep IntentDeterminationEngine standalone: port the single
expand_template helper into engine.py instead of importing it from
ovos-utils, which is only required by the optional opm.py pipeline
entrypoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a docs/ guide covering the Adapt pipeline plugin end to end:
concepts and theory for newcomers, a quickstart, the IntentBuilder
reference with worked examples, configuration, the messagebus protocol,
and an internals page deriving the confidence formula. Link it from the
README.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: expose DomainIntentDeterminationEngine as opm.pipeline entry point

Add `DomainAdaptPipeline`, a sibling of `AdaptPipeline` that wraps
adapt's `DomainIntentDeterminationEngine`. Each `skill_id` gets its own
sub-engine ("domain"); at match time every domain is scored in
parallel and a global argmax over the union of candidates wins. No
top-level router is involved.

Intent / vocab / regex registrations are routed by the skill_id prefix
of the intent label (`skill_id:intent_name`). Configurable via
`intents.ovos_adapt_domain_pipeline`.

* test: add flat-vs-domain engine benchmark

Port the keyword-intent benchmark dataset and add a comparison harness
that runs the same vocabulary and intents through IntentDeterminationEngine
and DomainIntentDeterminationEngine. Results are documented in
docs/benchmark.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* style: drop extraneous f-string prefix in benchmark

* test: add entity-overlap cases and flat-vs-domain head-to-head

Extend the benchmark dataset with utterances built around words
registered under multiple entity types across domains, the only inputs
where per-domain trie isolation can change tagging. Add a head-to-head
report listing the cases where the two engines disagree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: add two-stage hierarchical engine to the benchmark

Add a third runner that classifies the domain first, then resolves the
intent within only that domain, plus a stage-1 routing-accuracy metric.
Two-stage routing scores 74.5% against 79.3% for flat and parallel-domain:
hard routing misroutes 15% of utterances unrecoverably.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: redesign benchmark dataset with two-slot intents

Rework the dataset around two-slot intents (shared ACTION keyword plus
domain-distinctive OBJECT keyword) so single stray keywords no longer
over-trigger and domains are lexically separable. Replace the stage-1
router with a keyword-coverage classifier, which routes 98% of cases
correctly and lets the hierarchical engine suppress cross-domain false
positives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: add discriminating cases that separate the three engines

Add overlapping vocabulary (shared temperature keyword, optional room
slots) and three hand-crafted case sections, each built to give one
topology a clear edge: routing-hard commands where flat and domain beat
hierarchical, two-clause utterances where flat and domain diverge, and
bare-keyword non-commands where hierarchical's gate suppresses false
positives. The three engines now score 87.2 / 88.2 / 90.3 percent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: frame the engine comparison as a tuned reference, not a benchmark

State plainly that the dataset is hand-tuned to surface prediction
differences between the topologies, that the accuracy ordering is an
artifact of the discriminating-section mix, and how the dataset can be
composed to favour any engine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add HierarchicalIntentDeterminationEngine and pipeline

Make the two-stage hierarchical engine a first-class member of the
package, consistent with the flat and domain engines: a
HierarchicalIntentDeterminationEngine subclass of
DomainIntentDeterminationEngine, a HierarchicalAdaptPipeline subclass of
DomainAdaptPipeline, and a matching opm.pipeline entry point. The
benchmark now drives all three through the same engine API instead of
ad-hoc classifier glue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review on PR #37

- Serialise domain/parser reads and mutations under self.lock in the
  domain pipeline (candidate gathering, detach_intent, shutdown).
- Route regex vocab registrations by entity_type, not regex_str, so
  _resolve_entity_domain receives the prefix key it expects.
- Validate the intent->domain map is one-to-one before building it.
- Guard the stage-1 routing percentage against an empty match set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: cover the domain and hierarchical pipelines

Add docs/pipelines.md describing the three pipeline variants and when
to use each, and wire it into the index, configuration, internals, and
README alongside the engine-comparison reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…42)

Replace ovos_utils.lang.standardize_lang_tag (macro=True by default)
with ovos_spec_tools.standardize_lang, and replace the local
langcodes.closest_match + score<10 reconciliation in _get_closest_lang
with ovos_spec_tools.closest_lang.

Engine buckets are now keyed by the full normalized BCP-47 tag
(e.g. en-US) rather than the macro language (e.g. en). Registration
handlers reconcile the incoming lang to an existing bucket via
closest_lang, matching the policy used at match time.

BREAKING CHANGE: adapt engines now bucket by full BCP-47 tag. Resource
registrations whose lang differs from the configured pipeline lang
(e.g. registering en-GB vocab into an en-US pipeline) previously
collided by coincidence after macro-stripping and now go through the
per-match closest_lang reconciliation; configurations that depended
on the silent macro-collapse should add a matching entry to
secondary_langs or rely on closest_lang's region tolerance.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(deps): allow ovos-workshop 9.x (widen <9.0.0 -> <10.0.0)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(deps): drop ovos-workshop dependency

The adapt parser vendored its matching Intent/IntentBuilder/open_intent_envelope
from ovos-workshop, which forced an ovos-workshop version cap and coupled this
parser to a higher-level package. These primitives are native to the adapt
parser (the engine calls Intent.validate_with_tags), so they now live in
ovos_adapt.intent directly.

- ovos_adapt/intent.py: own the full matching Intent + IntentBuilder (with
  excludes support) and open_intent_envelope instead of importing from
  ovos_workshop.intents
- ovos_adapt/opm.py: import open_intent_envelope from ovos_adapt.intent
- tests: import IntentBuilder/Intent from ovos_adapt.intent
- pyproject: remove ovos-workshop dependency entirely (no cap to widen)

ovos-spec-tools (closest_lang/standardize_lang) is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: consume ovos-spec-tools intent primitives instead of duplicating

ovos_adapt.intent re-implemented the declarative INTENT-4 Intent /
IntentBuilder / open_intent_envelope from scratch even though
ovos-spec-tools (already a declared dependency) provides the canonical
versions. Only the adapt-engine tag-matching logic is genuinely
adapt-specific.

- Intent now subclasses ovos_spec_tools.intent.Intent, inheriting the
  name/requires/at_least_one/optional/excludes role lists and
  to_keyword_payload emission; it adds ONLY validate /
  validate_with_tags (plus the private tag-search helpers). adapt
  Intent instances are now isinstance(spec_tools.Intent) and expose the
  INTENT-4 keyword-payload surface.
- IntentBuilder subclasses ovos_spec_tools.intent.IntentBuilder and
  overrides only build() to return adapt's matching Intent.
- open_intent_envelope reuses the spec-tools parser (which accepts both
  legacy and INTENT-4 §5.2 wire keys) and re-wraps as adapt's Intent.
- Module helpers (is_entity, find_first_tag, find_next_tag,
  choose_1_from_each, resolve_one_of) are unchanged.
- Floor ovos-spec-tools to >=0.16.0a1 (first release carrying the
  consumed intent primitives).

spec-tools' Intent was cleanly subclassable: plain class (no slots/
frozen), constructor signature-compatible, and its field normalization
((type, attr) pairs for requires/optional, name tuples for
at_least_one, bare names for excludes) is exactly what adapt's
validate_with_tags reads. No spec-tools change required. All 93 tests
pass unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ovos-bus-client 2.4.x (Session.blacklisted_intents/blacklisted_skills are
optional SESSION-1 fields) can surface either blacklist as None, so the
'intent_type not in sess.blacklisted_intents' / 'skill not in
sess.blacklisted_skills' membership tests in match_intent raised
'TypeError: argument of type NoneType is not iterable' and aborted every
adapt match (the utterance then fell through to complete_intent_failure).
Treat a None blacklist as empty.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…stration (#48)

* fix: coerce foreign Intents at registration so direct registration works

ovos-workshop re-exports the bare ovos-spec-tools IntentBuilder, whose
Intent carries the same fields but lacks adapt's matching API
(validate / validate_with_tags). Skills registering over the bus are
unaffected (open_intent_envelope already rebuilds an adapt Intent from
the serialized message), but direct in-process register_intent_parser of
such an Intent raised ValueError. Rebuild any matching-API-less but
intent-shaped parser as an adapt Intent before the interface check;
native adapt Intents pass through unchanged and non-intents still raise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: declare bus-client runtime floor + lift pm/spec-tools floors

opm.py imports ovos_bus_client (MessageBusClient/SessionManager/Message)
at runtime; declare it directly with the migration-capable prerelease floor
(>=2.5.1a1,<3.0.0). That floor only resolves without --pre when the
ovos-plugin-manager floor is also lifted to the 2.x line (>=2.4.0a1),
otherwise pip selects an old pm that caps ovos-bus-client <2.0. Raise the
ovos-spec-tools floor to the documented baseline 0.16.1a2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
)

* feat: consume OVOS-INTENT-4 keyword registration (alongside legacy)

Subscribe to the OVOS-INTENT-4 spec registration topics
(ovos.intent.register.keyword, ovos.entity.register, the deregister
trio, and intent enable/disable) in addition to the legacy
register_vocab / register_intent / detach_* handlers, which are kept
untouched so un-migrated skills keep working.

The keyword handler translates the consolidated §5 payload into adapt's
split vocab + IntentBuilder model: required -> require, optional ->
optionally, one_of groups -> one_of, excluded -> exclude. Inline
samples are registered as adapt entities (which expand INTENT-1
(a|b)/[opt] templates), namespaced by skill_id so detach-by-skill
reaches them. Malformed payloads (no required and no one_of, missing
samples) are dropped with a WARN per §5.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(deps): bump to published merged versions (spec-tools 0.16.1a2, allow padacioso 2.0 / workshop 9)

* test: add INTENT-4 consumer e2e proving spec-topic register + match

Adds test/end2end/test_intent4_consume_e2e.py: boots a real MiniCroft on the
adapt pipeline (ovoscope E2EPipelineHarness) and asserts adapt CONSUMES the
OVOS-INTENT-4 spec registration topics, not just the legacy bus events:

- §5  ovos.intent.register.keyword -> utterance matches (one_of, excluded)
- legacy register_vocab/register_intent still matches (back-compat)
- §8.2 ovos.intent.deregister / §8.4 ovos.skill.deregister remove the intent
- §8.5 ovos.intent.disable suppresses + ovos.intent.enable re-arms
- §11  the keyword engine does NOT match a template-topic registration

xfail: §8.5 disable is registration-scoped in the spec but adapt scopes it to
the disable Message's Session, so a cross-session disable does not suppress.

Also fixes a latent crash exposed by the pinned stack: match_intent and the
spec disable/enable handlers assumed Session.blacklisted_intents was a list,
but ovos-bus-client>=2.4.0a1 defaults it to None -> 'argument of type
NoneType is not iterable'. Guarded all three sites.

Wires test/end2end/ into the ovoscope workflow and pins the e2e stack floor
(ovos-bus-client>=2.4.0a1, ovos-spec-tools>=0.16.1a2) in the test extra.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(test): pin ovos-workshop 9.0.0a1 to dodge IntentBuilder regression

ovos-workshop 9.0.1a2 changed IntentBuilder.build() to return an
ovos_spec_tools.intent.Intent that lacks .validate / .validate_with_tags.
ovos_adapt.engine.register_intent_parser enforces that duck-type, so under
9.0.1a2 *every* adapt registration (legacy register_intent AND the new
INTENT-4 ovos.intent.register.keyword) is rejected with
"... is not an intent parser", reddening the whole ovoscope e2e suite.

The bus-client>=2.4.0a1 floor drags the latest workshop prerelease (9.0.1a2)
in; pin the last good one (9.0.0a1, which still satisfies bus-client>=2.4.0a1)
in the test extra until workshop restores the validating Intent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: use adapt's own IntentBuilder in legacy-flow test

test_legacy_flow_still_works imported ovos_workshop.intents.IntentBuilder,
but the coverage job installs the package with no extras (install_extras: '')
so ovos-workshop is absent there, failing collection. ovos_adapt.intent
.IntentBuilder is the adapt-native drop-in (same serialized __dict__), so the
test no longer needs ovos-workshop to exercise the legacy register_intent path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: declare bus-client runtime floor + lift pm/test floors for 2.x

opm.py imports ovos_bus_client at runtime; declare it directly. The
prerelease floor (>=2.5.1a1) only resolves without --pre when the
ovos-plugin-manager floor is also lifted to the 2.x line (>=2.4.0a1),
otherwise pip selects an old pm that caps ovos-bus-client <2.0.
Bump the [test] INTENT-4 e2e bus-client floor 2.4.0a1 -> 2.5.1a1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
JarbasAl and others added 8 commits June 27, 2026 23:52
* fix: default config value

related PR: OpenVoiceOS/ovos-config#264

* fix: restore core_config reference broken by the config-default change

The __init__ change replaced 'core_config = Configuration()' with an
'intents'-scoped lookup but left lines reading core_config.get('lang') /
.get('secondary_langs') → NameError. Keep both: core_config for lang/secondary_langs,
intent_config for the plugin's own config block.
spec-tools crossed to 1.x; bus-client 2.6.0a1 and the rest of the
stack now require ovos-spec-tools>=1.1.0a1. Drop the <1.0.0 cap
(keep the floor) so the stack resolves.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant